﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using ProxyViewModel.Attributes;
using ProxyViewModel;
using System.Reflection;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace AutoViewModel.Tests {
	[TestClass]
	public class ValidationTester {
		#region Test Classes

		public class ViewModelA {
			[EmailValidator]
			public virtual string Email {
				get;
				set;
			}
		}

		public class ViewModelB : ViewModelA {
			[TestValueValidator]
			public virtual string TestValue {
				get;
				set;
			}
		}

		public class ViewModelC : ViewModelB {
			[InvalidValueValidator(BadValue = "val1")]
			[InvalidValueValidator(BadValue = "val2")]
			[InvalidValueValidator(BadValue = "val3")]
			public virtual string TestProp {
				get;
				set;
			}
		}

		public class TestValueValidator : ValidatorBaseAttribute {
			public override ValidationResult Validate(object input) {
				if (!(input is string)) {
					return new ValidationResult {
						IsValid = false,
						ValidationError = "Invalid type"
					};
				}

				if (((string)input).Contains("badvalue")) {
					return new ValidationResult {
						IsValid = false,
						ValidationError = "Bad value"
					};
				}

				return new ValidationResult { IsValid = true };
			}
		}

		public class InvalidValueValidatorAttribute : ValidatorBaseAttribute {
			public string BadValue {
				get;
				set;
			}

			public override ValidationResult Validate(object input) {
				if (!(input is string)) {
					return new ValidationResult {
						IsValid = false,
						ValidationError = "Invalid value type"
					};
				}


				if (((string)input).ToLower().Contains(BadValue.ToLower())) {
					return new ValidationResult {
						IsValid = false,
						ValidationError = string.Format("Invalid value '{0}'", input.ToString())
					};
				}

				return new ValidationResult {
					IsValid = true
				};
			}
		}

		#endregion

		[TestMethod]
		public void TestEmailLastValidationMessageAndIsValidEmailPropertiesAreGenerated() {
			ViewModelA viewModel = ProxyViewModelFactory.CreateProxyViewModel<ViewModelA>();
			PropertyInfo lastValidationMessageProperty = viewModel.GetType().GetProperty("EmailLastValidationMessage");
			PropertyInfo isValidProperty = viewModel.GetType().GetProperty("IsValidEmail");
			Assert.IsNotNull(lastValidationMessageProperty);
			Assert.IsNotNull(isValidProperty);
		}

		[TestMethod]
		public void TestValidationWasInvoked() {
			ViewModelA viewModel = ProxyViewModelFactory.CreateProxyViewModel<ViewModelA>();
			viewModel.Email = "this is invalid value";
		}

		[TestMethod]
		public void TestValidationPropertiesSetCorrectly() {
			ViewModelA viewModel = ProxyViewModelFactory.CreateProxyViewModel<ViewModelA>();
			viewModel.Email = "this is invalid value";
			PropertyInfo lastValidationMessageProperty = viewModel.GetType().GetProperty("EmailLastValidationMessage");
			PropertyInfo isValidProperty = viewModel.GetType().GetProperty("IsValidEmail");

			Assert.AreEqual((bool)isValidProperty.GetValue(viewModel, null), false);
			Assert.AreEqual((string)lastValidationMessageProperty.GetValue(viewModel, null), @"""this is invalid value"" is not a valid email");
		}

		[TestMethod]
		public void TestvalidationClearsOut() {
			ViewModelA viewModel = ProxyViewModelFactory.CreateProxyViewModel<ViewModelA>();
			viewModel.Email = "this is invalid value";
			PropertyInfo lastValidationMessageProperty = viewModel.GetType().GetProperty("EmailLastValidationMessage");
			PropertyInfo isValidProperty = viewModel.GetType().GetProperty("IsValidEmail");
			Assert.AreEqual((bool)isValidProperty.GetValue(viewModel, null), false);
			Assert.AreEqual((string)lastValidationMessageProperty.GetValue(viewModel, null), @"""this is invalid value"" is not a valid email");
			Assert.IsNull(viewModel.Email);

			viewModel.Email = "testemail@gmail.com";
			Assert.AreEqual((bool)isValidProperty.GetValue(viewModel, null), true);
			Assert.AreEqual((string)lastValidationMessageProperty.GetValue(viewModel, null), string.Empty);
		}

		[TestMethod]
		public void TestValidationPropertiesINotifyPropertyChanged() {
			ViewModelA viewModel = ProxyViewModelFactory.CreateProxyViewModel<ViewModelA>();

			bool validationMessageSet = false;
			bool isValidMessageSet = false;

			((INotifyPropertyChanged)viewModel).PropertyChanged += (s, e) => {
				if (e.PropertyName == "EmailLastValidationMessage") {
					validationMessageSet = true;
				}
				if (e.PropertyName == "IsValidEmail") {
					isValidMessageSet = true;
				}
			};

			viewModel.Email = "test email";
			Assert.IsTrue(validationMessageSet && isValidMessageSet);
		}

		[TestMethod]
		public void AssertInitiallyValid() {
			ViewModelB viewModelB = ProxyViewModelFactory.CreateProxyViewModel<ViewModelB>();
			PropertyInfo isValidEmail = viewModelB.GetType().GetProperty("IsValidEmail");
			PropertyInfo isValidTestValue = viewModelB.GetType().GetProperty("IsValidTestValue");
			bool bothValid = (bool)isValidEmail.GetValue(viewModelB, null) && (bool)isValidTestValue.GetValue(viewModelB, null);
			Assert.IsTrue(bothValid);
		}

		[TestMethod]
		public void AssertInvalidWithInvalidValues() {
			ViewModelB viewModelB = ProxyViewModelFactory.CreateProxyViewModel<ViewModelB>();
			PropertyInfo isValidEmail = viewModelB.GetType().GetProperty("IsValidEmail");
			PropertyInfo isValidTestValue = viewModelB.GetType().GetProperty("IsValidTestValue");
			bool bothValid = (bool)isValidEmail.GetValue(viewModelB, null) && (bool)isValidTestValue.GetValue(viewModelB, null);
			Assert.IsTrue(bothValid, "Initially both must be valid");

			viewModelB.Email = "invalid value";
			viewModelB.TestValue = "badvalue";

			Assert.IsTrue(viewModelB.Email == null && !(bool)isValidEmail.GetValue(viewModelB, null));
			Assert.IsTrue(viewModelB.TestValue == null && !(bool)isValidTestValue.GetValue(viewModelB, null));
		}

		[TestMethod]
		public void AssertMessageIsPassed() {
			ViewModelB viewModelB = ProxyViewModelFactory.CreateProxyViewModel<ViewModelB>();

			PropertyInfo isValidEmail = viewModelB.GetType().GetProperty("IsValidEmail");
			PropertyInfo isValidTestValue = viewModelB.GetType().GetProperty("IsValidTestValue");

			PropertyInfo emailLastValidation = viewModelB.GetType().GetProperty("EmailLastValidationMessage");
			PropertyInfo testValueLastValidationMessage = viewModelB.GetType().GetProperty("TestValueLastValidationMessage");

			bool bothValid = (bool)isValidEmail.GetValue(viewModelB, null) && (bool)isValidTestValue.GetValue(viewModelB, null);
			Assert.IsTrue(bothValid, "Initially both must be valid");

			viewModelB.Email = "invalid value";
			viewModelB.TestValue = "badvalue";

			Assert.AreEqual((string)emailLastValidation.GetValue(viewModelB, null), @"""invalid value"" is not a valid email");
			Assert.AreEqual((string)testValueLastValidationMessage.GetValue(viewModelB, null), @"Bad value");
		}

		[TestMethod]
		public void AssertMessageClearsOut() {
			ViewModelB viewModelB = ProxyViewModelFactory.CreateProxyViewModel<ViewModelB>();
			PropertyInfo isValidEmail = viewModelB.GetType().GetProperty("IsValidEmail");
			PropertyInfo isValidTestValue = viewModelB.GetType().GetProperty("IsValidTestValue");

			PropertyInfo emailLastValidation = viewModelB.GetType().GetProperty("EmailLastValidationMessage");
			PropertyInfo testValueLastValidationMessage = viewModelB.GetType().GetProperty("TestValueLastValidationMessage");

			viewModelB.Email = "invalid value";
			viewModelB.TestValue = "badvalue";

			bool bothValid = !(bool)isValidEmail.GetValue(viewModelB, null) && !(bool)isValidTestValue.GetValue(viewModelB, null);
			Assert.IsTrue(bothValid, "Must be both Invalid");

			Assert.AreEqual((string)emailLastValidation.GetValue(viewModelB, null), @"""invalid value"" is not a valid email");
			Assert.AreEqual((string)testValueLastValidationMessage.GetValue(viewModelB, null), @"Bad value");

			viewModelB.Email = "test@test.com";
			viewModelB.TestValue = "this@is.com";

			Assert.AreEqual((string)emailLastValidation.GetValue(viewModelB, null), string.Empty);
			Assert.AreEqual((string)testValueLastValidationMessage.GetValue(viewModelB, null), string.Empty);

			bothValid = (bool)isValidEmail.GetValue(viewModelB, null) && (bool)isValidTestValue.GetValue(viewModelB, null);
			Assert.IsTrue(bothValid, "Must be both valid");
		}

		[TestMethod]
		public void AssertValidationMessagesAreGenerated() {
			ViewModelB viewmodelb = ProxyViewModelFactory.CreateProxyViewModel<ViewModelB>();
			PropertyInfo emailValidationMessages = viewmodelb.GetType().GetProperty("EmailValidationErrors");
			PropertyInfo testValueValidationMessages = viewmodelb.GetType().GetProperty("TestValueValidationErrors");
			Assert.IsNotNull(emailValidationMessages);
			Assert.IsNotNull(testValueValidationMessages);
		}

		[TestMethod]
		public void AssertValidationErrorsAreSet() {
			ViewModelB viewmodelb = ProxyViewModelFactory.CreateProxyViewModel<ViewModelB>();
			PropertyInfo testValueValidationMessages = viewmodelb.GetType().GetProperty("TestValueValidationErrors");
			ObservableCollection<string> errors = (ObservableCollection<string>)testValueValidationMessages.GetValue(viewmodelb, null);
			viewmodelb.TestValue = "badvalue@gmail.com";
			Assert.IsTrue(errors.Count == 1);
		}

		[TestMethod]
		public void TestValidationInTwoViewModels() {
			ViewModelC v1 = ProxyViewModelFactory.CreateProxyViewModel<ViewModelC>();
			ViewModelC v2 = ProxyViewModelFactory.CreateProxyViewModel<ViewModelC>();

			// invalid properties
			// [InvalidValueValidator(BadValue = "val1")]
			// [InvalidValueValidator(BadValue = "val2")]
			// [InvalidValueValidator(BadValue = "val3")]

			v1.TestProp = "this value must be valid";
			v2.TestProp = "and this value must be valid as well";

			PropertyInfo validationErrorsProperty = v1.GetType().GetProperty("TestPropValidationErrors");
			PropertyInfo testPropIsvalid = v1.GetType().GetProperty("IsValidTestProp");
			PropertyInfo lastValidationMessage = v1.GetType().GetProperty("TestPropLastValidationMessage");

			Assert.IsNotNull(validationErrorsProperty);

			ObservableCollection<string> v1TestPropMessages = (ObservableCollection<string>)validationErrorsProperty.GetValue(v1, null);
			ObservableCollection<string> v2TestPropMessages = (ObservableCollection<string>)validationErrorsProperty.GetValue(v2, null);


			Assert.AreEqual(v1TestPropMessages.Count, 0);
			Assert.AreEqual(v2TestPropMessages.Count, 0);

			// changing v1 to invalid
			v1.TestProp = "val1, val2, val3";
			Assert.AreEqual(v1.TestProp, "this value must be valid", "Should not change because it is invalid");
			Assert.IsFalse((bool)testPropIsvalid.GetValue(v1, null), "Should be invalid");
			Assert.AreEqual(v1TestPropMessages.Count, 3, "Must have three validation errors");
			Assert.AreEqual((string)lastValidationMessage.GetValue(v1, null), "Invalid value 'val1, val2, val3'");

			// changing v2 to invalid
			v2.TestProp = "val1";
			Assert.AreEqual(v2.TestProp, "and this value must be valid as well", "Should not change because it is invalid");
			Assert.IsFalse((bool)testPropIsvalid.GetValue(v2, null), "Should be invalid");
			Assert.AreEqual(v2TestPropMessages.Count, 1, "Must have one validation error");
			Assert.AreEqual((string)lastValidationMessage.GetValue(v2, null), "Invalid value 'val1'");

			// changing v1 to valid
			v1.TestProp = "Now this is a valid value";
			Assert.AreEqual(v1.TestProp, "Now this is a valid value", "Must change and be a new value");
			Assert.IsTrue((bool)testPropIsvalid.GetValue(v1, null), "Should be valid");
			Assert.AreEqual(v1TestPropMessages.Count, 0, "Must have no validation errors");
			Assert.AreEqual((string)lastValidationMessage.GetValue(v1, null), string.Empty);

			// check if v2 is still invalid
			Assert.AreEqual(v2.TestProp, "and this value must be valid as well", "Should not change because it is invalid");
			Assert.IsFalse((bool)testPropIsvalid.GetValue(v2, null), "Should be invalid");
			Assert.AreEqual(v2TestPropMessages.Count, 1, "Must have one validation error");
			Assert.AreEqual((string)lastValidationMessage.GetValue(v2, null), "Invalid value 'val1'");
		}

		[TestMethod]
		public void TestIsValidatableGenerated() {
			ViewModelB viewModelb = ProxyViewModelFactory.CreateProxyViewModel<ViewModelB>();
			Assert.IsTrue(viewModelb is IValidatableViewModel);
		}

		[TestMethod]
		public void TestViewModelsMethodsArePopulated() {
			ViewModelC viewModelb = ProxyViewModelFactory.CreateProxyViewModel<ViewModelC>();
			Assert.AreEqual(((IValidatableViewModel)viewModelb).ValidationMessages.Count, 0, "Must be valid here");
			Assert.IsTrue(((IValidatableViewModel)viewModelb).IsViewModelValid, "Must be valid here");

			viewModelb.TestProp = "val1, val2, val3";
			viewModelb.Email = "invalid email";

			Assert.IsFalse(((IValidatableViewModel)viewModelb).IsViewModelValid, "must be not valid");
			Assert.AreEqual(((IValidatableViewModel)viewModelb).ValidationMessages.Count, 4, "Must have four validation messages");

			viewModelb.Email = "valid@email.com";

			Assert.IsFalse(((IValidatableViewModel)viewModelb).IsViewModelValid, "must still be not valid");
			Assert.AreEqual(((IValidatableViewModel)viewModelb).ValidationMessages.Count, 3, "Must have three validation messages");

			viewModelb.TestProp = "MEGAVAL";

			Assert.IsTrue(((IValidatableViewModel)viewModelb).IsViewModelValid, "must be valid");
			Assert.AreEqual(((IValidatableViewModel)viewModelb).ValidationMessages.Count, 0, "Must have 0 validation messages");
		}

		[TestMethod]
		public void TestIsViewModelValidPropertyChangedInvoked() {
			ViewModelC viewModelb = ProxyViewModelFactory.CreateProxyViewModel<ViewModelC>();

			bool isValidPropertyWasCalled = false;

			((INotifyPropertyChanged)viewModelb).PropertyChanged += (s, e) => {
				isValidPropertyWasCalled = true;
			};

			Assert.AreEqual(((IValidatableViewModel)viewModelb).ValidationMessages.Count, 0, "Must be valid here");
			Assert.IsTrue(((IValidatableViewModel)viewModelb).IsViewModelValid, "Must be valid here");

			viewModelb.TestProp = "val1, val2, val3";
			viewModelb.Email = "invalid email";

			Assert.IsTrue(isValidPropertyWasCalled);
			Assert.IsFalse(((IValidatableViewModel)viewModelb).IsViewModelValid, "must be not valid");
			Assert.AreEqual(((IValidatableViewModel)viewModelb).ValidationMessages.Count, 4, "Must have four validation messages");

			isValidPropertyWasCalled = false;

			viewModelb.Email = "valid@email.com";
			Assert.IsTrue(isValidPropertyWasCalled);
			Assert.IsFalse(((IValidatableViewModel)viewModelb).IsViewModelValid, "must still be not valid");
			Assert.AreEqual(((IValidatableViewModel)viewModelb).ValidationMessages.Count, 3, "Must have three validation messages");

			isValidPropertyWasCalled = false;

			viewModelb.TestProp = "MEGAVAL";
			Assert.IsTrue(isValidPropertyWasCalled);
			Assert.IsTrue(((IValidatableViewModel)viewModelb).IsViewModelValid, "must be valid");
			Assert.AreEqual(((IValidatableViewModel)viewModelb).ValidationMessages.Count, 0, "Must have 0 validation messages");
		}
	}
}
